package com.turtle3d.exporter;

import java.io.IOException;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;

import javax.media.j3d.BranchGroup;
import javax.media.j3d.GeometryArray;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Point3f;
import javax.vecmath.TexCoord2f;
import javax.vecmath.TexCoord3f;
import javax.vecmath.Vector3f;

/**
 * <p>Wavefront .obj file format exporter.</p>
 * 
 * <p>Exports geometry, uv coordinates and materials. Does not export normals.</p>
 * 
 * @author Marek Paterczyk <marek.paterczyk@gmail.com>
 *
 */
public class ObjExporter
{

	private ArrayHash<Point3f> vertices = new ArrayHash<Point3f>();	
	
	private ArrayHash<ObjectMaterial> materials = new ArrayHash<ObjectMaterial>();	
	
	private ArrayHash<TexCoord2f> texCoords = new ArrayHash<TexCoord2f>();
	
	/**
	 * Separate objects that can be moved independently.
	 */
	private ArrayList<FaceObject> faceObjects = new ArrayList<FaceObject>();		
	
	DecimalFormat floatFormat = new DecimalFormat("0.000000");
	PrintStream output = System.out;
	PrintStream materialOutput = System.out;	
	
	public ObjExporter(){
		DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols();
		decimalFormatSymbols.setDecimalSeparator('.');
		
		floatFormat.setDecimalFormatSymbols(decimalFormatSymbols);
	}

	/**
	 * <p>Adds objects from branch.</p>
	 * 
	 * <p>Works under assumption, that branchGroup has only TransformGroup objects as children.
	 * Also only first child of those TransformGroups is saved, rest is ignored. </p>
	 * 
	 * @param branchGroup
	 */
	public void add(BranchGroup branchGroup){
		
		Enumeration children = branchGroup.getAllChildren();

		while (children.hasMoreElements())
		{
			Object o = children.nextElement();
			TransformGroup t = ((TransformGroup) o);
			Transform3D transform3D = new Transform3D();
			t.getTransform(transform3D);
			Matrix4f transform = new Matrix4f();
			transform3D.get(transform);
			
			Shape3D shape = (Shape3D) ((TransformGroup) o).getChild(0);					
			
			addShape3D(shape, transform);
		}
								
	}
	
	/**
	 * Export added objects to wavefront format (.obj & .mtl)
	 * 
	 * @param path Path to a directory
	 * @param fileName File name, without extension
	 * @throws IOException
	 */
	public void export(String path, String fileName) throws IOException{
		
		//geometry
		output = new PrintStream(path+"/"+fileName+".obj");		
	
		output.println("mtllib "+fileName+".mtl");
		output.println();
		for(Point3f v:vertices.getArraylist())
			output.println(createVElement(v));			
		
		output.println();
		
		//texture coordinates
		for(TexCoord2f v:texCoords.getArraylist())
			output.println(createVtElement(v));			
		
		output.println();
						
		int i=0;
		Collections.sort(faceObjects);
		Integer lastMaterialIndex=null;
		for(FaceObject o:faceObjects){
			if(!(lastMaterialIndex!=null && lastMaterialIndex==o.getMaterialIndex())){
				output.println("g "+(o.getName()+"_"+(i++)));
				output.println("usemtl mtl"+o.getMaterialIndex());
				output.println("s 1");
			lastMaterialIndex = o.getMaterialIndex();
			}
			for(Face face:o.getFaces())
				output.println(createFElement(face));
			output.println();
			
			if(!(lastMaterialIndex!=null && lastMaterialIndex==o.getMaterialIndex())){
				output.println("g");
			}
		}
				
		//materials
		materialOutput = new PrintStream(path+"/"+fileName+".mtl");
		for(ObjectMaterial material: materials.getArraylist()){
			materialOutput.println("newmtl mtl"+materials.getIndex(material));
			
			//diffuse color								
			materialOutput.println(createKElement(material.diffuseColor, "d"));
			
			//ambient
			materialOutput.println(createKElement(material.ambientColor, "a"));
			
			//specular
			materialOutput.println(createKElement(material.specularColor, "s"));
			
			materialOutput.println("illum 2");
			materialOutput.println("d "+material.opacity);
			if(material.textureName!=null)
				materialOutput.println(createMapKElement("d", material.textureName));
			materialOutput.println();
		}
		
		output.close();
		materialOutput.close();				
	}
	
	/**
	 * Adds a Shape3D object for export.
	 * @param shape3D
	 * @param transform
	 */
	public void addShape3D(Shape3D shape3D, Matrix4f transform){
		GeometryArray a = (GeometryArray) shape3D.getGeometry();

		Point3f verticesArray[] = new Point3f[a.getVertexCount()];
		//Vector3f normalsArray[] = new Vector3f[a.getVertexCount()];
		TexCoord2f texCoordsArray[] = new TexCoord2f[a.getVertexCount()];
		for (int i = 0; i < verticesArray.length; i++){
			verticesArray[i] = new Point3f();
			//normalsArray[i] = new Vector3f();
			texCoordsArray[i] = new TexCoord2f();
		}

		a.getCoordinates(0, verticesArray);
		//a.getNormals(0, normalsArray);
				
		try{
			a.getTextureCoordinates(0, 0, texCoordsArray);
		}catch(ArrayIndexOutOfBoundsException e){
			//no texture coordinates			
		}
		
		FaceObject faceObject = new FaceObject(shape3D.getName());
		
		for(int i=0;i<verticesArray.length; i+=3){			
			transform.transform(verticesArray[i]);
			int v_pos1 = addVertex(verticesArray[i]);
			int c_pos1 = addTexCoord(texCoordsArray[i]);
			
			transform.transform(verticesArray[i+1]);
			int v_pos2 = addVertex(verticesArray[i+1]);
			int c_pos2 = addTexCoord(texCoordsArray[i+1]);
			
			transform.transform(verticesArray[i+2]);
			int v_pos3 = addVertex(verticesArray[i+2]);
			int c_pos3 = addTexCoord(texCoordsArray[i+2]);			
						
			faceObject.addFace(new Face(v_pos1, v_pos2, v_pos3, c_pos1, c_pos2, c_pos3));			
		}				
		
		int m_pos = addMaterial(new ObjectMaterial(shape3D.getAppearance()));
		faceObject.setMaterialIndex(m_pos);
		
		faceObjects.add(faceObject);
		
	}
	
	private int addTexCoord(TexCoord2f texCoord){
		return texCoords.add(texCoord);
	}
	
	private int addVertex(Point3f vertex){
		return vertices.add(vertex);
	}	
	
	private int addMaterial(ObjectMaterial material){
		return materials.add(material);
	}
	
	private String createVElement(Point3f vertex){		
		return "v  "+floatFormat.format(vertex.x)+" "+floatFormat.format(vertex.y)+" "+floatFormat.format(vertex.z);
	}	
	
	private String createFElement(Face face){			
		//return "f "+face.v1+"//"+face.n1+" "+face.v2+"//"+face.n2+" "+face.v3+"//"+face.n3;		
		return "f "+face.v1+"/"+face.c1+" "+face.v2+"/"+face.c2+" "+face.v3+"/"+face.c3;
	}
	
	private String createKElement(Color3f color, String colorType){
		return "K"+colorType+"  "+floatFormat.format(color.x)+" "+floatFormat.format(color.y)+" "+floatFormat.format(color.z);
	}
	
	private String createVtElement(TexCoord2f texCoord){
		return "vt "+texCoord.x+" "+texCoord.y;
	}
	
	private String createMapKElement(String type, String fileName){
		return "map_K"+type+" "+fileName;
	}

}
